iTesting软件测试知识分享

爬虫入门 --打造网站自生成系统(一)

以前IT界流传着一句笑话:”当你一个朋友忽然消失了的时候,他不是去了华为就是去了阿里”。 对于没有在这两个宇宙大厂的我来说,这么长时间没消息,要么我弃坑不更了,要么折腾新玩意儿去了。
弃坑是不可能弃坑的,所以当你感叹这么好的公众号怎么也弃坑的时候,我都会拿一个新玩意儿来取悦你 :)

爬虫其实不算是新的东西了, 网上也有很多的教程,都很详尽,那么我为什么还要拿出来说呢?因为我发现大多数教材都是教你如何从网络上爬取内容,然后就结束了。 但是我们爬下来的内容是要使用的啊?
这方面的就很少。还记得我之前分享过的博客开发系列吗?正好,我们把这两个结合起来,一起来看看,如何用爬虫打造专属自己的自动化博客生成系统。

本系列分为如下模块:
1.介绍爬虫框架scrapy的原理,及简单使用。
2.自动分析爬取的文章,分类并转化格式为适合博客的markdown格式。
3.自动发布博客,并介绍如何绑定域名。
4.每天躺在床上看自己的博客“更新”。

今天我们先看第一个, 爬虫框架Scrapy。

首先,爬虫是什么呢? 爬虫的原理是什么?

爬虫是一个程序,用来获取网站的信息。 爬虫的原理一般是根据一定的分析算法找出用户想要的URL,放到一个队列里,然后按照一定的策略选择进一步要抓取的URL,直到满足停止条件。 然后对所有抓取的网页进行分析,过滤,存储并建立索引,方便后续查询,检索。

什么是scrapy

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

scrapy原理 (此部分为官网文档,及网络摘抄)

Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下(绿线是数据流向):

各部分详细说明如下:

一此完整的运行流程如下:

如何安装

1
pip install scrapy

入门教程(官网摘取架构各部分作用,个人提供练习)

创建项目

在开始爬取之前,您必须创建一个新的Scrapy项目。 进入您打算存储代码的目录中,运行下列命令:

1
scrapy startproject _myspider


1
2
3
4
5
scrapy.cfg 项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
items.py 设置数据存储模板,用于结构化数据,如:Django的Model
pipelines 数据处理行为,如:一般结构化的数据持久化
settings.py 配置文件,如:递归的层数、并发数,延迟下载等
spiders 爬虫目录,如:创建文件,编写爬虫规则

定义Item

Item 是保存爬取到的数据的容器;其使用方法和python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。

类似在ORM中做的一样,您可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个Item。 (如果不了解ORM, 不用担心,您会发现这个步骤非常简单)

首先根据需要从dmoz.org获取到的数据对item进行建模。 我们需要从dmoz中获取名字,url,以及网站的描述。 对此,在item中定义相应的字段。编辑 _myspider 目录中的 items.py 文件:

1
2
3
4
5
6
import scrapy
class MyspiderItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()

一开始这看起来可能有点复杂,但是通过定义item, 您可以很方便的使用Scrapy的其他方法。而这些方法需要知道您的item的定义。

编写第一个爬虫(Spider)

Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。

其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。

为了创建一个Spider,您必须继承 scrapy.Spider 类, 且定义以下三个属性:
name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。

以下为我们的第一个Spider代码,保存在 _myspider/spiders 目录下的 mine.py 文件中:
我们要从10tiao.com这个网站爬取首页上的所有公号文章,并按照如下方式保存

1
{"author": "作者:谢宝友", "create_date": "2018-05-24 13:21", "name": "GitChat精选", content:...}

start_requests 这个请求接收需要爬取的第一个页面,然后交给parse()处理,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# -*- coding: utf-8 -*-
import json
import scrapy
from scrapy import Selector
from _myspider.items import MyspiderItem
class QuotesSpider(scrapy.Spider):
name = "quotes"
def start_requests(self): # start_requests这个函数也可以不要,只要有start_urls = [。。]即可。
url = "http://10tiao.com"
yield scrapy.Request(url, self.parse)
def parse(self, response):
info = MyspiderItem()
for i in response.xpath(".//*[@id='id-content-wrap']//article"):
info["name"] = Selector(text=i.extract()).xpath("//*[@id='id-author-name']//text()").extract()[0]
info["title"] = Selector(text=i.extract()).xpath("//a//text()").extract()[0]
#此url为了抽取标题下面的正文内容,url获取后,交给callback函数进一步处理。
url = Selector(text=i.extract()).xpath("//h1[@class='post-title text-left']/a/@href").extract()[0]
#注意meta,用来传递值到下一个处理函数。
yield scrapy.Request(url, meta={'info1':info}, callback=self.parse_url_contents)
def parse_url_contents(self, response):
info = MyspiderItem()
info1 = response.meta['info1']
for i in response.xpath(".//*[@id='img-content']"):
#Scrapy提供了Selector选择器供我们筛选出目标选项。具体请参考Selector文档
info['create_date'] = Selector(text=i.extract()).xpath("//*[@id='post-date']/text()").extract()[0]
info['author'] = Selector(text=i.extract()).xpath("//*[@id='author']/text()").extract()[0]
info['content'] = Selector(text=i.extract()).xpath("//*[@id='js_content']").extract()[0]
info["name"] = info1["name"]
yield info

注意:
Request中meta参数的作用是传递信息给下一个函数,使用过程可以理解成:把需要传递的信息赋值给这个叫meta的变量,但meta只接受字典类型的赋值,因此要把待传递的信息改成“字典”的形式,即:meta={‘key1’:value1,’key2’:value2}

如果想在下一个函数中取出value1,只需得到上一个函数的meta[‘key1’]即可,因为meta是随着Request产生时传递的,下一个函数得到的Response对象中就会有meta,即response.meta,取value1则是value1=response.meta[‘key1’]

存储结果(Pipeline)

Item pipeline 的主要责任是负责处理 spider 抽取的 Item,主要任务是清理、验证和持久化数据。当页面被 spider 解析后,将被发送到 pipeline,每个 pipeline 的组件都是由一个简单的方法组成的Python类。pipeline 获取Item,执行相应的方法,并确定是否需要在 pipeline中继续执行下一步或是直接丢弃掉不处理。

1
2
3
4
5
6
7
8
9
10
11
12
import json
import codecs
class MyspiderPipeline(object):
def __init__(self):
#这里简单的保存到result.json文件里。
self.file = codecs.open('result.json', 'wb', encoding='utf-8')
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(line)
return item

激活Item Pipeline组件

在settings.py文件中,往ITEM_PIPELINES中添加项目管道的类名,激活项目管道组件

1
2
3
ITEM_PIPELINES = {
'_myspider.pipelines.MyspiderPipeline': 300,
}

运行爬虫

1
2
3
scrapy crawl quotes #quotes是你在spider里定义的爬虫名字。
你也可以把运行的结果dump到文件里:
scrapy crawl quotes -o result.json

写在最后

  1. 看下我们的运行结果, result.json节选:
    1
    2
    3
    4
    5
    #为了排版方便,我把content里的内容注释掉了。
    {"author": "Google Play", "create_date": "2018-05-24 13:00", "name": "GitChat精选"}
    {"author": "张 璁", "create_date": "2018-05-24 13:34", "name": "GitChat精选"}
    {"author": "机器之心", "create_date": "2018-05-24 12:23", "name": "GitChat精选"}
    {"author": "机器之心", "create_date": "2018-05-24 12:23", "name": "GitChat精选"}
  2. 是不是很简单?确实简单, 爬虫的爬取,收集信息比较直观,但是爬虫最重要的的问题就是经常被ban,至于如何解决,目前没有一劳永逸的方法,读者朋友们可以网络上搜索解决。
  3. 本文未涉及登录问题有些网站需要登录获取authentication token后才能继续爬取(我比较喜欢这个,方便控制), 有两个办法,一个是用requests直接发请求获取后保存使用。第二scrapy貌似提供了登录的方法,这个我还没有研究, 大家可以自行研究。
🐶 您的支持将鼓励我继续创作 🐶
-------------评论, 吐槽, 学习交流,请关注微信公众号 iTesting-------------
请关注微信公众号 iTesting wechat
扫码关注,跟作者互动